package hikari.instruments;

import com.gooddies.wiring.annotations.Wire;
import com.gooddies.wiring.annotations.WiringComponent;
import hikari.gui.panels.HierarchyTree;
import hikari.managers.EngineProperties;
import hikari.managers.Manager3D;
import hikari.managers.SelectionManager;
import hikari.objects3d.Basic3dObject;
import hikari.utils.Utils;
import java.awt.Cursor;
import java.awt.Point;
import java.util.ArrayList;
import java.util.List;
import org.swig.jirr.ISceneCollisionManager;
import org.swig.jirr.ISceneNode;
import org.swig.jirr.IVideoDriver;
import org.swig.jirr.aabbox3df;
import org.swig.jirr.line3df;
import org.swig.jirr.recti;
import org.swig.jirr.triangle3df;
import org.swig.jirr.vector2di;
import org.swig.jirr.vector3df;

/**
 * @author sad
 */
@WiringComponent(singleton = true, lazy = true)
public class SelectInstrument extends CameraObserveInstrument {
    @Wire
    private EngineProperties engineProperties;
    @Wire
    private Manager3D manager3D;
    @Wire
    private SelectionManager selectionManager;
    @Wire
    private HierarchyTree hierarchyTree;
    @Wire
    protected Utils utils;
    private ISceneNode trySelect(int x, int y) {
        ISceneCollisionManager cm = manager3D.getSceneManager().getSceneCollisionManager();
        line3df ray = cm.getRayFromScreenCoordinates(new vector2di(x, y));
        vector3df point = new vector3df();
        triangle3df triangle = new triangle3df();
        return cm.getSceneNodeAndCollisionPointFromRay(ray, point, triangle);
    }
    
    private void drawSelectionBox(IVideoDriver driver) {
        Basic3dObject node = selectionManager.getCurrentSelectedObject();
        if (node != null) {
            aabbox3df box = node.getBoundingBox();
            if (box != null) {
                box = new aabbox3df(box.getMinEdge(), box.getMaxEdge());
                driver.setTransformationAsInNode(node.getMeshSceneNode());
                driver.setMaterial(engineProperties.getSMaterialProperty("selectionBoxMaterial"));
                box.setMinEdge(box.getMinEdge().addOperator(new vector3df(-0.5f, -0.5f, -0.5f)));
                box.setMaxEdge(box.getMaxEdge().addOperator(new vector3df(0.5f, 0.5f, 0.5f)));
                driver.draw3DBox(box);
            }
        }
    }
    
    @Override
    public void mouseMove(int x, int y, int dx, int dy, int modifier) {
        processSelection(x, y, dx, dy, modifier);
    }
    
    protected void processSelection(int x, int y, int dx, int dy, int modifier) {
        mouseMoved = true;
        if (!pressed) {
            ISceneNode node = trySelect(x, y);
            if (node != null) {
                setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
            } else {
                setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
            }
            
            super.mouseMove(x, y, dx, dy, modifier);
        } else {
            currentPosition = new Point(x, y);
        }
    }
    
    private boolean pressed = false;
    private boolean mouseMoved = false;
    private Point originPosition = null;
    private Point currentPosition = null;
    
    private void multipleSelection(final recti bound) {
        bound.repair();
        final ISceneCollisionManager cm = manager3D.getSceneManager().getSceneCollisionManager();
        final List<Basic3dObject> collidedObjects = new ArrayList<>();
        hierarchyTree.forEachNodes(new HierarchyTree.HierarchyTreeNodeEnumarator() {
            @Override
            public boolean onNode(Basic3dObject node) {
                aabbox3df box = node.getBoundingBox();
                vector3df p1 = box.getMaxEdge();
                vector3df p2 = box.getMinEdge();
                vector3df abs = node.getMeshSceneNode().getAbsolutePosition();
                if (isBoxInside2dRect(cm, bound, p1.addOperator(abs), p2.addOperator(abs))) {
                    collidedObjects.add(node);
                }
                
                return true;
            }
        });
        
        if (!collidedObjects.isEmpty()) {
            if (collidedObjects.size() == 1) {
                selectionManager.select(collidedObjects.get(0));
            }
        }
    }
    
    private boolean isBoxInside2dRect(ISceneCollisionManager cm, recti rect, vector3df p1, vector3df p2) {
        vector2di pos1 = cm.getScreenCoordinatesFrom3DPosition(p1);
        vector2di pos2 = cm.getScreenCoordinatesFrom3DPosition(p2);
        return rect.isPointInside(pos1) && rect.isPointInside(pos2);
    }
    
    @Override
    public void mouseUp(int x, int y, int button, int modifier) {
        if (mouseMoved == false) {
            if (isLeftMouseDown(modifier) && !isRightMouseDown(modifier)) {
                ISceneNode node = trySelect(x, y);
                Basic3dObject object = utils.ISceneNodeToBasic3dObject(node);
                selectionManager.select(object);
            }
        } else {
            if (pressed == true && isLeftMouseDown(modifier) && !isRightMouseDown(modifier)) {
                try {
                    multipleSelection(new recti(originPosition.x, originPosition.y, currentPosition.x, currentPosition.y));
                } catch (Exception ex) {
                    ex.toString();
                }
            }
        }
        
        pressed = false;
        originPosition = null;
        currentPosition = null;
    }
    
    @Override
    public void mouseDown(int x, int y, int button, int modifier) {
        if (isLeftMouseDown(modifier) && !isRightMouseDown(modifier)) {
            pressed = true;
            mouseMoved = false;
            originPosition = new Point(x, y);
        }
        super.mouseDown(x, y, button, modifier);
    }
    
    private void drawSelectionRectangle(IVideoDriver driver, Point p1, Point p2) {
        driver.resetTransformMatrix();
        driver.setMaterial(engineProperties.getSMaterialProperty("selectionBoxMaterial"));
        driver.draw2DRectangleOutline(new recti(p1.x, p1.y, p2.x, p2.y));
    }
    
    @Override
    public void onAfterRenderBeforeGui() {
        super.onAfterRenderBeforeGui();
        IVideoDriver driver = manager3D.getVideoDriver();
        drawSelectionBox(driver);
        if (pressed && originPosition != null && currentPosition != null) {
            drawSelectionRectangle(driver, originPosition, currentPosition);
        }
    }
}
